package com.sun.redis.config.annotation;


import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * <分布式redis锁-AOP>
 *
 * @author 施瑞
 * @date 2021/6/3
 */
@Aspect
@Component
@Order(1)
public class DistributedRedisLockAop {

    /**
     * mLog
     */
    Logger mLog = LoggerFactory.getLogger(DistributedRedisLockAop.class);


    @Resource
    private RedissonClient redisson;

    /**
     * redis锁
     *
     * @param joinPoint            切入点
     * @param distributedRedisLock 分布式redis锁注解类
     * @return Object   返回参数
     * @throws Exception 异常
     */
    @Around(value = "@annotation(DistributedRedisLock) && @annotation(distributedRedisLock)")
    public Object redisLock(ProceedingJoinPoint joinPoint, DistributedRedisLock distributedRedisLock) throws Exception {
        Object output = null;
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] arguments = joinPoint.getArgs();

            // 获取锁的key
            StringBuilder stringBuilder = new StringBuilder();
            if (StringUtils.isNotBlank(distributedRedisLock.publicPrefix())) {
                Object publicPrefix = parseSpel(distributedRedisLock.publicPrefix(), method, arguments);
                stringBuilder.append(publicPrefix);
            }
            if (StringUtils.isNotBlank(distributedRedisLock.lockKeyPrefix())) {
                Object lockKeyPrefix = parseSpel(distributedRedisLock.lockKeyPrefix(), method, arguments);
                stringBuilder.append(lockKeyPrefix);
            }
            if (StringUtils.isNotBlank(distributedRedisLock.lockKeySuffix())) {
                Object lockKeySuffix = parseSpel(distributedRedisLock.lockKeySuffix(), method, arguments);
                stringBuilder.append(lockKeySuffix);
            }
            String lockKey = stringBuilder.toString();
            RLock lock = redisson.getLock(lockKey);
            mLog.info("开始加锁[{}]", lockKey);
            try {
                boolean ifLock = lock.tryLock(distributedRedisLock.waitTime(), TimeUnit.SECONDS);

                if (ifLock) {
                    mLog.info(Thread.currentThread().getName() + ": 加锁成功[{}]", lockKey);
                    // 具体执行
                    output = joinPoint.proceed();
                } else {
                    mLog.info(Thread.currentThread().getName()+"");
                    //还是建议业务测自己进行重试，总体重试的时间不能超过默认的锁失效时间60秒，否则重试必然成功，因为前面的锁已经失效了，但其实业务在60秒内并未执行完毕，需要优化业务，或者降低锁的颗粒度
                    int errorCount = 1;
                    //重试2次
                    while (errorCount <= 2) {
                        try {
                            mLog.info("已有任务正在执行，等待100ms并开始第" + errorCount + "次重试");
                            //等待100ms
                            Thread.sleep(100L);
                            boolean ifReTryLock = lock.tryLock(distributedRedisLock.waitTime(), TimeUnit.SECONDS);
                            if (ifReTryLock) {
                                output = joinPoint.proceed();
                                return output;
                            }
                        } catch (InterruptedException ie) {
                            mLog.error("sleep InterruptedException：{}", ie);
                        } finally {
                            errorCount++;
                        }
                    }
                    mLog.info("加锁失败[{}]，继续执行方法，设置第一个参数为[{}]", lockKey, "已有任务正在执行，请勿重复请求");
                    arguments[0] = "已有任务正在执行，请勿重复请求";
                    output = joinPoint.proceed(arguments);
                    mLog.info("线程[{}]未获取到锁，目前锁详情信息为：{}", Thread.currentThread().getName(), lock);
                }
            } catch (Exception e) {
                mLog.error("加锁出错:{}", e.getMessage());
            } finally {
                mLog.info("尝试解锁[{}]", lockKey);
                // 先判断是否已经持有锁 如果持有 则释放
                if ( lock.isHeldByCurrentThread()) {
                    try {
                        lock.unlock();
                        mLog.info("[{}]解锁成功", lockKey);
                    } catch (IllegalMonitorStateException e) {
                        mLog.error("解锁出错:{}", e.getMessage());
                    }
                } else {

                    System.out.println(Thread.currentThread().getName() + ": 当前线程并未持有该锁，无法释放");
                }
            }
        } catch (Throwable e) {
            mLog.error("aop redis distributed lock error:{}", e.getLocalizedMessage());
            throw new Exception(e.getLocalizedMessage());
        }
        return output;
    }

    /**
     * 解析SpEL表达式
     *
     * @param key    SpEL表达式
     * @param method 反射得到的方法
     * @param args   反射得到的方法参数
     * @return 解析后SpEL表达式对应的值
     */
    public static Object parseSpel(String key, Method method, Object[] args) {
        // 创建解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 通过Spring的LocalVariableTableParameterNameDiscoverer获取方法参数名列表
        LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer
                = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        // 构造上下文
        EvaluationContext context = new StandardEvaluationContext();
        if (args.length == parameterNames.length) {
            for (int i = 0, len = args.length; i < len; i++) {
                // 使用setVariable方法来注册自定义变量
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        return parser.parseExpression(key).getValue(context);
    }


}
